#ifndef SORBET_PARSER_PRISM_TRANSLATOR_H
#define SORBET_PARSER_PRISM_TRANSLATOR_H

#include "absl/types/span.h"
#include "core/errors/parser.h"
#include "parser/Node.h" // To clarify: these are Sorbet Parser nodes, not Prism ones.
#include "parser/prism/Parser.h"
#include <memory>
#include <optional>

extern "C" {
#include "prism.h"
}

namespace sorbet::parser::Prism {

class Translator final {
    const Parser &parser;
    // This context holds a reference to the GlobalState allocated up the call stack, which is why we don't allow
    // Translator objects to be copied or moved.
    core::MutableContext ctx;

    // The errors that were found by Prism during parsing
    const absl::Span<const ParseError> parseErrors;

    // Whether to directly desugar during in the Translator, or wait until the usual `Desugar.cc` code path.
    const bool directlyDesugar;

    // When true, desugar less aggressively so that refactoring actions operate on the code that was actually written,
    // instead of synthetic code that was generated by the desugarer.
    const bool preserveConcreteSyntax;

    // Unique counters used to create synthetic names via `ctx.state.freshNameUnique`.
    // - The storage integers either store an "active" count used by a translator and some of its children,
    //   or a dummy value.
    // - The pointer variables point to the "active" count for each translator,
    //   which is either pointing to its own storage, or to a parent's storage.
    uint16_t parserUniqueCounterStorage;  // Minics the `Builder::Impl.uniqueCounter_` in `parser/Builder.cc`
    uint32_t desugarUniqueCounterStorage; // Minics the `DesugarContext.uniqueCounter`  in `ast/desugar/Desugar.cc`
    uint16_t &parserUniqueCounter;        // Points to the active `parserUniqueCounterStorage`
    uint32_t &desugarUniqueCounter;       // Points to the active `desugarUniqueCounterStorage`

    // Context variables
    const core::LocOffsets enclosingMethodLoc;   // The location of the method we're in, or `none()`
    const core::NameRef enclosingMethodName;     // The name of the method we're in, or `noName()`
    const core::NameRef enclosingBlockParamName; // The name of the block param of the method we're in, or `noName()`
    const bool isInModule = false;   // True if we're in a Module definition. False for classes and singleton classes
    const bool isInAnyBlock = false; // True if we're in a `{ }`/`do end` block

    Translator(Translator &&) = delete;                 // Move constructor
    Translator(const Translator &) = delete;            // Copy constructor
    Translator &operator=(Translator &&) = delete;      // Move assignment
    Translator &operator=(const Translator &) = delete; // Copy assignment
public:
    Translator(const Parser &parser, core::MutableContext ctx, const absl::Span<const ParseError> parseErrors,
               bool directlyDesugar, bool preserveConcreteSyntax)
        : parser(parser), ctx(ctx), parseErrors(parseErrors), directlyDesugar(directlyDesugar),
          preserveConcreteSyntax(preserveConcreteSyntax), parserUniqueCounterStorage(1), desugarUniqueCounterStorage(1),
          parserUniqueCounter(this->parserUniqueCounterStorage),
          desugarUniqueCounter(this->desugarUniqueCounterStorage) {}

    // Translates the given AST from Prism's node types into the equivalent AST in Sorbet's legacy parser node types.
    std::unique_ptr<parser::Node> translate(pm_node_t *node);

private:
    // This private constructor is used for creating child translators with modified context.
    // uniqueCounterStorage is passed as a dummy value and is never used
    Translator(const Translator &parent, bool resetDesugarUniqueCounter, core::LocOffsets enclosingMethodLoc,
               core::NameRef enclosingMethodName, core::NameRef enclosingBlockParamName, bool isInModule,
               bool isInAnyBlock)
        : parser(parent.parser), ctx(parent.ctx), parseErrors(parent.parseErrors),
          directlyDesugar(parent.directlyDesugar), preserveConcreteSyntax(parent.preserveConcreteSyntax),
          parserUniqueCounterStorage(9999), desugarUniqueCounterStorage(resetDesugarUniqueCounter ? 1 : 999999),
          parserUniqueCounter(parent.parserUniqueCounter),
          desugarUniqueCounter(resetDesugarUniqueCounter ? this->desugarUniqueCounterStorage
                                                         : parent.desugarUniqueCounter),
          enclosingMethodLoc(enclosingMethodLoc), enclosingMethodName(enclosingMethodName),
          enclosingBlockParamName(enclosingBlockParamName), isInModule(isInModule), isInAnyBlock(isInAnyBlock) {}

    template <typename SorbetNode, typename... TArgs>
    std::unique_ptr<parser::Node> make_node_with_expr(ast::ExpressionPtr desugaredExpr, TArgs &&...args) const;

    template <typename SorbetNode, typename... TArgs>
    std::unique_ptr<parser::Node> make_unsupported_node(TArgs &&...args) const;

    core::LocOffsets translateLoc(pm_location_t loc) const;
    core::LocOffsets translateLoc(const uint8_t *start, const uint8_t *end) const;

    parser::NodeVec translateMulti(pm_node_list prismNodes);
    void translateMultiInto(NodeVec &sorbetNodes, absl::Span<pm_node_t *> prismNodes);

    std::pair<std::unique_ptr<parser::Params>, core::NameRef /* enclosingBlockParamName */>
    translateParametersNode(pm_parameters_node *paramsNode, core::LocOffsets location);

    std::tuple<ast::MethodDef::PARAMS_store, ast::InsSeq::STATS_store, bool>
    desugarParametersNode(NodeVec &params, bool attemptToDesugarParams);

    core::LocOffsets findItParamUsageLoc(pm_statements_node *statements);

    std::array<core::LocOffsets, 9> findNumberedParamsUsageLocs(core::LocOffsets loc, pm_statements_node *statements,
                                                                uint8_t maxParamNumber);

    NodeVec translateNumberedParametersNode(pm_numbered_parameters_node *numberedParamsNode,
                                            pm_statements_node_t *statements,
                                            ast::MethodDef::PARAMS_store *paramsStore);

    ast::ExpressionPtr desugarSymbolProc(pm_symbol_node *symbol);

    NodeVec translateArguments(pm_arguments_node *node, pm_node *blockArgumentNode = nullptr);
    parser::NodeVec translateKeyValuePairs(pm_node_list_t elements);

    ast::ExpressionPtr desugarArray(core::LocOffsets location, absl::Span<pm_node_t *> prismElements,
                                    ast::Array::ENTRY_store elements);
    ast::ExpressionPtr desugarHash(core::LocOffsets loc, NodeVec &kvPairs);

    std::unique_ptr<parser::Node> translateCallWithBlock(pm_node_t *prismBlockOrLambdaNode,
                                                         std::unique_ptr<parser::Node> sendNode);

    NodeVec translateEnsure(pm_begin_node *beginNode);
    std::unique_ptr<parser::Node> translateRescue(pm_begin_node *parentBeginNode);
    std::unique_ptr<parser::Node> translateStatements(pm_statements_node *stmtsNode, bool inlineIfSingle = true,
                                                      core::LocOffsets overrideLocation = core::LocOffsets::none());

    std::unique_ptr<parser::Node> translateRegexpOptions(pm_location_t closingLoc);
    std::unique_ptr<parser::Node> translateRegexp(core::LocOffsets location, core::LocOffsets contentLoc,
                                                  pm_string_t content, pm_location_t closingLoc);

    template <typename PrismNode>
    std::unique_ptr<parser::Mlhs> translateMultiTargetLhs(PrismNode *node, core::LocOffsets location);

    template <typename PrismAssignmentNode, typename SorbetLHSNode>
    std::unique_ptr<parser::Node> translateAssignment(pm_node_t *node);

    template <typename PrismAssignmentNode, typename SorbetAssignmentNode, typename SorbetLHSNode>
    std::unique_ptr<parser::Node> translateAnyOpAssignment(PrismAssignmentNode *node, core::LocOffsets location,
                                                           std::unique_ptr<parser::Node> lhs);

    // Translate operator assignment targeting an indexed expression (e.g., `a[0] += 1`).
    template <typename PrismAssignmentNode, typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateIndexAssignment(pm_node_t *node, core::LocOffsets location);

    // Translate AndAsgn/OrAsgn operator assignments (e.g., `x &&= y`, `x ||= y`).
    template <typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateAndOrAssignment(core::LocOffsets location, std::unique_ptr<parser::Node> lhs,
                                                           std::unique_ptr<parser::Node> rhs);

    template <typename PrismConstantNode, typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateConstantAssignment(pm_node_t *node, core::LocOffsets location);

    // Translate a constant path assignment, e.g. `A::B = 1`
    template <typename PrismConstantPathNode, typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateConstantPathAssignment(pm_node_t *node, core::LocOffsets location);

    // Translates regular assignments, not including e.g. `x[i] = y`, `x &&= y`, `x ||= y`, etc.
    template <typename PrismVariableNode, typename SorbetAssignmentNode, typename SorbetLHSNode>
    std::unique_ptr<parser::Node> translateVariableAssignment(pm_node_t *node, core::LocOffsets location);

    // Translates an assignment to a method call, e.g. `x.y = z`
    template <typename PrismAssignmentNode, typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateSendAssignment(pm_node_t *node, core::LocOffsets location);

    // Translate operator assignment targeting a safe navigation call (e.g., `a&.b += 1`).
    template <typename PrismAssignmentNode, typename SorbetAssignmentNode>
    std::unique_ptr<parser::Node> translateCSendAssignment(PrismAssignmentNode *callNode, core::LocOffsets location,
                                                           std::unique_ptr<parser::Node> receiver, core::NameRef name,
                                                           core::LocOffsets messageLoc);

    template <typename PrismLhsNode, typename SorbetLHSNode, bool checkForDynamicConstAssign = false>
    std::unique_ptr<parser::Node> translateConst(PrismLhsNode *node);
    core::NameRef translateConstantName(pm_constant_id_t constantId);

    // Generates a unique name for a `parser::Node`.
    core::NameRef nextUniqueParserName(core::NameRef original);

    // Generates a unique name for a directly desugared `ast::ExpressionPtr`.
    core::NameRef nextUniqueDesugarName(core::NameRef original);

    // Structure for holding the scaffolding needed for op-assignment desugaring
    struct OpAsgnScaffolding {
        core::NameRef temporaryName;
        ast::InsSeq::STATS_store statementBody;
        uint16_t numPosArgs;
        ast::Send::ARGS_store readArgs;
        ast::Send::ARGS_store assgnArgs;
    };

    // Copy arguments in op-assignment desugaring
    OpAsgnScaffolding copyArgsForOpAsgn(ast::Send *s);

    // Translate OpAsgn operator assignments
    template <typename SorbetAssignmentNode, typename PrismAssignmentNode>
    std::unique_ptr<parser::Node> translateOpAssignment(PrismAssignmentNode *node, core::LocOffsets location,
                                                        std::unique_ptr<parser::Node> lhs,
                                                        std::unique_ptr<parser::Node> rhs);

    // Pattern-matching
    // ... variations of the main translation functions for pattern-matching related nodes.
    std::unique_ptr<parser::Node> patternTranslate(pm_node_t *node);
    parser::NodeVec patternTranslateMulti(pm_node_list prismNodes);
    void patternTranslateMultiInto(NodeVec &sorbetNodes, absl::Span<pm_node_t *> prismNodes);

    std::string_view sliceLocation(pm_location_t loc) const;

    // Helper function for creating if nodes with optional desugaring
    std::unique_ptr<parser::Node> translateIfNode(core::LocOffsets location, std::unique_ptr<parser::Node> predicate,
                                                  std::unique_ptr<parser::Node> ifTrue,
                                                  std::unique_ptr<parser::Node> ifFalse);

    std::pair<core::NameRef, core::LocOffsets> translateSymbol(pm_symbol_node *symbol);

    // String interpolation desugaring
    sorbet::ast::ExpressionPtr desugarDString(core::LocOffsets loc, pm_node_list prismNodeList);

    // Multi-assignment desugaring
    ast::ExpressionPtr desugarMlhs(core::LocOffsets loc, parser::Mlhs *lhs, ast::ExpressionPtr rhs);

    // Extracts the desugared expressions out of a "scope" (class/sclass/module) body.
    std::optional<ast::ClassDef::RHS_store> desugarScopeBodyToRHSStore(pm_node *prismBodyNode,
                                                                       std::unique_ptr<parser::Node> &scopeBody);

    void reportError(core::LocOffsets loc, const std::string &message) const;

    // Helper to determine whether to use super or untypedSuper based on context
    core::NameRef maybeTypedSuper() const;

    // Context management helpers. These return a copy of `this` with some change to the context.
    bool isInMethodDef() const;
    Translator enterMethodDef(bool isSingletonMethod, core::LocOffsets methodLoc, core::NameRef methodName,
                              core::NameRef enclosingBlockParamName) const;
    Translator enterBlockContext() const;
    Translator enterModuleContext() const;
    Translator enterClassContext() const;
};

} // namespace sorbet::parser::Prism
#endif // SORBET_PARSER_PRISM_TRANSLATOR_H
